/*
 * Code for blog.techhead.biz
 * Distributed under BSD-style license
 */

package biz.techhead.funcy;

import static biz.techhead.funcy.Tuples.*;

/**
 * Easily call functions of various parameter lengths.
 * Convert a {@link FuncE} to a {@link Func}.
 * Compose functions.
 * Various, etc.
 *
 * @author Jonathan Hawkes <jhawkes at techhead.biz>
 */
public class Funcs {

    private static final Func<?,?> IDENTITY = new Func<Object,Object>() {
        public Object call(Object in) {
            return in;
        }
    };

    /**
     * Returns an identity transformation function (returns the input as
     * output unchanged).
     *
     * @param <T> The type of the input paramater and output
     */
    @SuppressWarnings("unchecked")
    public static <T> Func<T,T> identity() {
        return ( Func<T,T> ) IDENTITY;
    }

    /**
     * Returns the negation of a predicate function.
     */
    public static <I> Func<Boolean,I> not(final Func<Boolean,I> f) {
        return new Func<Boolean,I>() {

            public Boolean call(I in) {
                return !f.call(in);
            }
        };
    }

    /**
     * Returns the negation of a predicate function.
     */
    @SuppressWarnings("unchecked")
    public static <I,X extends Throwable>
            FuncE<Boolean,I,X> not(final FuncE<Boolean,I,X> f) {

        if (f instanceof Func)
            return ( FuncE<Boolean,I,X> ) not( (Func)f );

        return new FuncE<Boolean,I,X>() {

            public Boolean call(I in) throws X {
                return !f.call(in);
            }
        };
    }
    
    /**
     * Returns a new function that pipes the result of f as an input to g.
     * 
     * @param <O> The type of the output
     * @param <I> The type of the input
     * @param <T> The type of the intermediate result (output of f, input of g)
     * @param <X> The type of the Throwable thrown
     * @return g( f(i) )
     */
    @SuppressWarnings("unchecked")
    public static <O,I,T,X extends Throwable>
            FuncE<O,I,X> compose(final FuncE<T,? super I,? extends X> f,
                       final FuncE<? extends O,? super T,? extends X> g) {

        if ( (f instanceof Func) && (g instanceof Func) )
            return ( FuncE<O,I,X> ) compose( (Func)f, (Func)g );

        return new FuncE<O,I,X>() {

            public O call(I in) throws X {
                return g.call( f.call(in) );
            }
        };
    }

    /**
     * Returns a new function that pipes the result of f as an input to g.
     * 
     * @param <O> The type of the output
     * @param <I> The type of the input
     * @param <T> The type of the intermediate result (output of f, input of g)
     * @param <X> The type of the Throwable thrown
     * @return g( f(i) )
     */
    @SuppressWarnings("unchecked")
    public static <O,I,T,X extends Throwable>
            FuncE<O,I,X> compose(final Func<T,? super I> f,
                      final FuncE<? extends O,? super T,? extends X> g) {

        if (g instanceof Func)
            return ( FuncE<O,I,X> ) compose( (Func)f, (Func)g );

        return new FuncE<O,I,X>() {

            public O call(I in) throws X {
                return g.call( f.call(in) );
            }
        };
    }

    /**
     * Returns a new function that pipes the result of f as an input to g.
     * 
     * @param <O> The type of the output
     * @param <I> The type of the input
     * @param <T> The type of the intermediate result (output of f, input of g)
     * @param <X> The type of the Throwable thrown
     * @return g( f(i) )
     */
    @SuppressWarnings("unchecked")
    public static <O,I,T,X extends Throwable>
            FuncE<O,I,X> compose(final FuncE<T,? super I,? extends X> f,
                                 final Func<? extends O,? super T> g) {

        if (f instanceof Func)
            return ( FuncE<O,I,X> ) compose( (Func)f, (Func)g );

        return new FuncE<O,I,X>() {

            public O call(I in) throws X {
                return g.call( f.call(in) );
            }
        };
    }

    /**
     * Returns a new function that pipes the result of f as an input to g.
     * 
     * @param <O> The type of the output
     * @param <I> The type of the input
     * @param <T> The type of the intermediate result (output of f, input of g)
     * @return g( f(i) )
     */
    public static <O,I,T> Func<O,I> compose(final Func<T,? super I> f,
                                  final Func<? extends O,? super T> g) {
        return new Func<O,I>() {

            public O call(I in) {
                return g.call( f.call(in) );
            }
        };
    }

    /**
     * Converts a {@link FuncE} that may throw checked exceptions into
     * a {@link Func} which may not.  Any checked exceptions are converted
     * into the runtime {@link CheckedBaggageException}.
     *
     * @param <O> The type of the output of the function
     * @param <I> The type of the input of the function
     * @param func a FuncE which may or may not throw checked exceptions
     * @return a new Func which does not throw any checked exceptions
     *         (they have all been converted to CheckedBaggageException)
     */
    @SuppressWarnings("unchecked")
    public static <O,I>
            Func<O,I> asFunc(final FuncE<O,I,? extends Throwable> func) {

        if (func instanceof Func) return ( Func<O,I> ) func;
        
        return new Func<O,I>() {

            @Override
            public O call(I in) {
                try {
                    return func.call(in);
                } catch (Throwable ex) {
                    if (ex instanceof RuntimeException)
                        throw (RuntimeException) ex;
                    if (ex instanceof Error) throw (Error) ex;
                    throw new CheckedBaggageException(ex);
                }
            }
        };
    }

    /**
     * Use this method to call any {@link FuncE} that expects no arguments.
     * The signature of these functions should be {@code Func<O,Tuples.T0>},
     * {@code Func<0,Object>} or their {@code FuncE} equivalents.
     *
     * @param <O> The output type of the function
     * @param <X> The exception type of the function
     * @param func The function to call
     * @throws X If {@code func.call(Tuples.T0)} throws X
     * @throws NullPointerException if func is null
     */
    public static <O,X extends Throwable>
            O call(FuncE<O,? super T0,X> func) throws X {
        return func.call( T0 );
    }

    /**
     * Calls a function that expects one or fewer arguments.  This method
     * represents a unique case, because it will call the given function with
     * a {@link Tuples.T1}, which is a Tuple that contains a single value.
     * It is unlikely that many {@link FuncE} signatures will be written
     * {@code Func<O,T1<A>>} instead of simply {@code Func<O,A>}.  One reason
     * a programmer <em>might</em> want to do this is to be able to accept one
     * required argument and several optional arguments.
     *
     * @param <O> The result type
     * @param <A> The type of the first parameter
     * @param <X> The type of exception which may be thrown
     * @param func The function to call
     * @param a The first argument to supply
     * @return The result of the function call
     * @throws X If calling the given function causes X to be thrown
     * @throws NullPointerException if func is null
     */
    public static <O,A,X extends Throwable>
            O call(FuncE<O,? super T1<A>,X> func, A a) throws X {
        return func.call( T(a) );
    }

    /**
     * Calls a function that accepts two or fewer arguments.
     * 
     * @param <O> The result type
     * @param <A> The type of the first parameter
     * @param <B> The type of the second parameter
     * @param <X> The type of exception which may be thrown
     * @param func The function to call
     * @param a The first argument to supply
     * @param b The second argument to supply
     * @return The result of the function call
     * @throws X If calling the fiven function causes X to be thrown
     * @throws NullPointerException if func is null
     */
    public static <O,A,B,X extends Throwable>
            O call(FuncE<O,? super T2<A,B>,X> func, A a, B b) throws X {
        return func.call( T(a,b) );
    }

    /**
     * Calls a function that accepts three or fewer arguments.
     * 
     * @param <O> The result type
     * @param <A> The type of the first parameter
     * @param <B> The type of the second parameter
     * @param <C> The type of the third parameter
     * @param <X> The type of exception which may be thrown
     * @param func The function to call
     * @param a The first argument to supply
     * @param b The second argument to supply
     * @param c The third argument to supply
     * @return The result of the function call
     * @throws X If calling the fiven function causes X to be thrown
     * @throws NullPointerException if func is null
     */
    public static <O,A,B,C,X extends Throwable>
            O call(FuncE<O,? super T3<A,B,C>,X> func, A a, B b, C c) throws X {
        return func.call( T(a,b,c) );
    }

    /**
     * Calls a function that accepts four or fewer arguments.
     * 
     * @param <O> The result type
     * @param <A> The type of the first parameter
     * @param <B> The type of the second parameter
     * @param <C> The type of the third parameter
     * @param <D> The type of the fourth parameter
     * @param <X> The type of exception which may be thrown
     * @param func The function to call
     * @param a The first argument to supply
     * @param b The second argument to supply
     * @param c The third argument to supply
     * @param d The fourth argument to supply
     * @return The result of the function call
     * @throws X If calling the fiven function causes X to be thrown
     * @throws NullPointerException if func is null
     */
    public static <O,A,B,C,D,X extends Throwable>
            O call(FuncE<O,? super T4<A,B,C,D>,X> func,
            A a, B b, C c, D d) throws X {
        return func.call( T(a,b,c,d) );
    }

    /**
     * Calls a function that accepts five or fewer arguments.
     * 
     * @param <O> The result type
     * @param <A> The type of the first parameter
     * @param <B> The type of the second parameter
     * @param <C> The type of the third parameter
     * @param <D> The type of the fourth parameter
     * @param <E> The type of the fifth parameter
     * @param <X> The type of exception which may be thrown
     * @param func The function to call
     * @param a The first argument to supply
     * @param b The second argument to supply
     * @param c The third argument to supply
     * @param d The fourth argument to supply
     * @param e The fifth argument to supply
     * @return The result of the function call
     * @throws X If calling the fiven function causes X to be thrown
     * @throws NullPointerException if func is null
     */
    public static <O,A,B,C,D,E,X extends Throwable>
            O call(FuncE<O,? super T5<A,B,C,D,E>,X> func,
            A a, B b, C c, D d, E e) throws X {
        return func.call( T(a,b,c,d,e) );
    }

    /**
     * Calls a function that accepts six or fewer arguments.
     * 
     * @param <O> The result type
     * @param <A> The type of the first parameter
     * @param <B> The type of the second parameter
     * @param <C> The type of the third parameter
     * @param <D> The type of the fourth parameter
     * @param <E> The type of the fifth parameter
     * @param <F> The type of the sixth parameter
     * @param <X> The type of exception which may be thrown
     * @param func The function to call
     * @param a The first argument to supply
     * @param b The second argument to supply
     * @param c The third argument to supply
     * @param d The fourth argument to supply
     * @param e The fifth argument to supply
     * @param f The sixth argument to supply
     * @return The result of the function call
     * @throws X If calling the fiven function causes X to be thrown
     * @throws NullPointerException if func is null
     */
    public static <O,A,B,C,D,E,F,X extends Throwable>
            O call(FuncE<O,? super T6<A,B,C,D,E,F>,X> func,
            A a, B b, C c, D d, E e, F f) throws X {
        return func.call( T(a,b,c,d,e,f) );
    }

    /**
     * Calls a function that accepts seven or fewer arguments.
     * 
     * @param <O> The result type
     * @param <A> The type of the first parameter
     * @param <B> The type of the second parameter
     * @param <C> The type of the third parameter
     * @param <D> The type of the fourth parameter
     * @param <E> The type of the fifth parameter
     * @param <F> The type of the sixth parameter
     * @param <G> The type of the seventh parameter
     * @param <X> The type of exception which may be thrown
     * @param func The function to call
     * @param a The first argument to supply
     * @param b The second argument to supply
     * @param c The third argument to supply
     * @param d The fourth argument to supply
     * @param e The fifth argument to supply
     * @param f The sixth argument to supply
     * @param g The seventh argument to supply
     * @return The result of the function call
     * @throws X If calling the fiven function causes X to be thrown
     * @throws NullPointerException if func is null
     */
    public static <O,A,B,C,D,E,F,G,X extends Throwable>
            O call(FuncE<O,? super T7<A,B,C,D,E,F,G>,X> func,
            A a, B b, C c, D d, E e, F f, G g) throws X {
        return func.call( T(a,b,c,d,e,f,g) );
    }

    /**
     * Calls a function that accepts eight or fewer arguments.
     * 
     * @param <O> The result type
     * @param <A> The type of the first parameter
     * @param <B> The type of the second parameter
     * @param <C> The type of the third parameter
     * @param <D> The type of the fourth parameter
     * @param <E> The type of the fifth parameter
     * @param <F> The type of the sixth parameter
     * @param <G> The type of the seventh parameter
     * @param <H> The type of the eighth parameter
     * @param <X> The type of exception which may be thrown
     * @param func The function to call
     * @param a The first argument to supply
     * @param b The second argument to supply
     * @param c The third argument to supply
     * @param d The fourth argument to supply
     * @param e The fifth argument to supply
     * @param f The sixth argument to supply
     * @param g The seventh argument to supply
     * @param h The eighth argument to supply
     * @return The result of the function call
     * @throws X If calling the fiven function causes X to be thrown
     * @throws NullPointerException if func is null
     */
    public static <O,A,B,C,D,E,F,G,H,X extends Throwable>
            O call(FuncE<O,? super T8<A,B,C,D,E,F,G,H>,X> func,
            A a, B b, C c, D d, E e, F f, G g, H h) throws X {
        return func.call( T(a,b,c,d,e,f,g,h) );
    }

    /**
     * Calls a function that accepts nine or fewer arguments.
     * 
     * @param <O> The result type
     * @param <A> The type of the first parameter
     * @param <B> The type of the second parameter
     * @param <C> The type of the third parameter
     * @param <D> The type of the fourth parameter
     * @param <E> The type of the fifth parameter
     * @param <F> The type of the sixth parameter
     * @param <G> The type of the seventh parameter
     * @param <H> The type of the eighth parameter
     * @param <I> The type of the ninth parameter
     * @param <X> The type of exception which may be thrown
     * @param func The function to call
     * @param a The first argument to supply
     * @param b The second argument to supply
     * @param c The third argument to supply
     * @param d The fourth argument to supply
     * @param e The fifth argument to supply
     * @param f The sixth argument to supply
     * @param g The seventh argument to supply
     * @param h The eighth argument to supply
     * @param i The ninth argument to supply
     * @return The result of the function call
     * @throws X If calling the fiven function causes X to be thrown
     * @throws NullPointerException if func is null
     */
    public static <O,X extends Throwable,A,B,C,D,E,F,G,H,I>
            O call(FuncE<O,? super T9<A,B,C,D,E,F,G,H,I>,X> func,
            A a, B b, C c, D d, E e, F f, G g, H h, I i) throws X {
        return func.call( T(a,b,c,d,e,f,g,h,i) );
    }
}
